﻿using System;
using Net.System;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using AOT;
using ikcpcb = KCP.IKCPCB;
using static KCP.KCP;

namespace Net.Client
{
    /// <summary>
    /// kcp客户端
    /// </summary>
    public unsafe class KcpClient : ClientBase
    {
        private ikcpcb* kcp;
        private IntPtr user;

        ~KcpClient()
        {
            ReleaseKcp();
        }

        protected override void OnSocketConnect(string ipAddress, int port)
        {
            base.OnSocketConnect(ipAddress, port);
            ReleaseKcp();
            var handle = GCHandle.Alloc(this, GCHandleType.Normal);
            user = GCHandle.ToIntPtr(handle);
            kcp = ikcp_create(0, (void*)user);
            kcp->output = &Output;
            ikcp_wndsize(kcp, 128, 128);
            ikcp_nodelay(kcp, 1, 10, 2, 1);
            kcp->rx_minrto = 10;
            kcp->fastresend = 1;
        }

        public unsafe delegate int outputHook(byte* buffer, int length, ikcpcb* kcp, void* user);

        [MonoPInvokeCallback(typeof(outputHook))]
        public static unsafe int Output(byte* buffer, int length, ikcpcb* kcp, void* user)
        {
            var client = GCHandle.FromIntPtr((nint)user).Target as KcpClient;
            client.trafficStatistics.SentBytesPerSecond += length;
            client.trafficStatistics.SentPacketsPerSecond++;
#if NET5_0_OR_GREATER
            var span = new ReadOnlySpan<byte>((void*)buffer, length);
            return client.Client.Send(span, SocketFlags.None);
#else
            var bytes = new byte[length];
            Unsafe.CopyBlock(ref bytes[0], ref *buffer, (uint)length);
            return client.Client.SendTo(bytes, SocketFlags.None, client.RemotePoint);
#endif
        }

        public override void ReceiveHandler()
        {
            if (Client == null || kcp == null)
                return;
            if (!Client.Connected)
            {
                if (Connected)
                    ConnectLost(SocketError.Disconnecting);
                return;
            }
            if (Client.Poll(performance, SelectMode.SelectRead))
            {
                var segment = BufferPool.Take(Config.ReceiveBufferSize);
                segment.Count = Client.Receive(segment.Buffer, 0, segment.Length, SocketFlags.None, out SocketError error);
                if (error != SocketError.Success | segment.Count == 0)
                {
                    BufferPool.Push(segment);
                    if (Connected) //如果是连接完成状态下才能触发连接中断, 要不然还没连接成功就触发连接中断就很不正常
                        ConnectLost(error);
                    return;
                }
                fixed (byte* data = &segment.Buffer[0])
                    ikcp_input(kcp, data, segment.Count);
                BufferPool.Push(segment);
            }
            int len;
            if ((len = ikcp_peeksize(kcp)) > 0)
            {
                var segment = BufferPool.Take(len);
                fixed (byte* p1 = &segment.Buffer[0])
                {
                    segment.Count = ikcp_recv(kcp, p1, len);
                    ReceiveHandler(ref segment);
                    BufferPool.Push(segment);
                }
            }
        }

        public override void OnNetworkTick()
        {
            base.OnNetworkTick();
            if (kcp != null)
                ikcp_update(kcp, (uint)Environment.TickCount);
        }

        protected override void SendByteData(ISegment buffer)
        {
            fixed (byte* p = &buffer.Buffer[0])
            {
                int count = ikcp_send(kcp, p, buffer.Count);
                if (count < 0)
                    OnSendErrorHandle?.Invoke(buffer);
            }
        }

        public override void Close(bool await = true, int millisecondsTimeout = 100)
        {
            base.Close(await, millisecondsTimeout);
            ReleaseKcp();
        }

        private void ReleaseKcp()
        {
            if (kcp == null)
                return;
            ikcp_release(kcp);
            kcp = null;
            GCHandle.FromIntPtr(user).Free();
        }
    }
}
